﻿#include "AutoScaleSimulator.h"
#include <utility>
#include <atomic>
#include <vector>
#include <cassert>
#include <iostream>

namespace AutoScale
{
	struct ScaleRuleSlot
	{
		ScaleRule Rule;
		int32_t Interval;
		int32_t LastCheckStep;

		ScaleRuleSlot(ScaleRule const& rule, int32_t interval)
			:Rule(rule), Interval(interval), LastCheckStep(0)
		{

		}

		void ApplyRule(int32_t step, int32_t loadPercentage, int32_t& instanceCount, bool& ruleApplied)
		{
			if (LastCheckStep + Interval <= step)
			{
				LastCheckStep = step;
				if (!ruleApplied)
					ruleApplied = Rule.ApplyScale(loadPercentage, instanceCount);
			}
		}
	};

	class Simulator::SimulatorImpl
	{
	protected:
		SimulatorImpl(int32_t minInstance, int32_t maxInstance);
		void AddRule(ScaleRule const& rule, int32_t intervalSeconds);
		void OneStep();
		ServerStatus GetServerStatus() const;
		LoadStatus GetRecentLoadStatus() const;
		bool Finished() const;
		void SetLoadGenerator(LoadGenerator&& generator);
		friend class Simulator;
	private:
		void AddLoad(ServiceLoad const& load);
		void StepAt(int32_t step);
		int32_t RequestAtStep(int32_t step) const;
		int32_t step_;
		std::vector<ScaleRuleSlot> rules_;
		std::vector<int32_t> totalRequests_;
		std::vector<int32_t> instances_;
		LoadGenerator generator_;
		int32_t requestOfLastStep_;
		const int32_t maxInstances_;
		const int32_t minInstances_;
	};


	ScaleRule ScaleRule::Make(ScaleType type, int32_t value, RuleOpcode op, int32_t threshold)
	{
		ScaleRule rule;
		rule.type_ = type;
		rule.value_ = value;
		rule.opcode_ = op;
		rule.threshold_ = threshold;
		return rule;
	}

	bool ScaleRule::ApplyScale(int32_t loadPercentage, int32_t& instances) const
	{
		bool applyRule = false;
		switch (opcode_)
		{
		case RuleOpGreater:
			applyRule = loadPercentage > threshold_;
			break;
		case RuleOpGreaterOrEqual:
			applyRule = loadPercentage >= threshold_;
			break;
		case RuleOpLess:
			applyRule = loadPercentage < threshold_;
			break;
		case RuleOpLessOrEqual:
			applyRule = loadPercentage <= threshold_;
			break;
		}
		if (applyRule)
		{
			if (type_ == ScaleByCount)
			{
				instances = instances + value_;
			}
			else if (type_ == ScaleByPercent)
			{
				int32_t val = value_ < -100 ? -100 : value_;
				instances = (instances * (100 + val))/100;
			}
		}
		return applyRule;
	}

	LoadGenerator::LoadGenerator():
		stepStart_(0), stepEnd_(0), stepInterval_(0)
	{

	}

	LoadGenerator::LoadGenerator(int32_t startStep, int32_t endStep, int32_t intervalStep, std::function<int32_t(int32_t, int32_t)>&& valueFunc, std::function<int32_t(void)>&& durationFunc):
		stepStart_(startStep), stepEnd_(endStep), stepInterval_(intervalStep), valueFunc_(std::move(valueFunc)), durationFunc_(std::move(durationFunc))
	{
	}

	bool LoadGenerator::NextLoad(int32_t step, ServiceLoad& load)
	{
		if (step >= stepStart_ && step < stepEnd_ && stepInterval_>0 && (step - stepStart_) % stepInterval_ == 0)
		{
			load.DurationSteps = durationFunc_();
			load.RequestCount = valueFunc_(step - stepStart_, stepEnd_ - stepStart_);
			return true;
		}
		return false;
	}

	Simulator::Simulator(int32_t minInstance, int32_t maxInstance) :internal_(new SimulatorImpl(minInstance, maxInstance))
	{
	}

	Simulator::~Simulator()
	{
		delete internal_;
		internal_ = nullptr;
	}

	void Simulator::AddRule(ScaleRule const& rule, int32_t intervalSeconds)
	{
		internal_->AddRule(rule, intervalSeconds);
	}

	void Simulator::OneStep()
	{
		internal_->OneStep();
	}

	ServerStatus Simulator::GetServerStatus() const
	{
		return internal_->GetServerStatus();
	}

	LoadStatus Simulator::GetRecentLoadStatus() const
	{
		return internal_->GetRecentLoadStatus();
	}

	void Simulator::SetLoadGenerator(LoadGenerator&& generator)
	{
		return internal_->SetLoadGenerator(std::move(generator));
	}

	bool Simulator::Finished() const
	{
		return internal_->Finished();
	}

	Simulator::SimulatorImpl::SimulatorImpl(int32_t minInstance, int32_t maxInstance) : 
		step_(0),
		requestOfLastStep_(0),
		minInstances_(minInstance),
		maxInstances_(maxInstance)
	{
		assert(minInstance > 0);
		assert(maxInstance >= minInstance);
		this->totalRequests_.reserve(4096);
		this->instances_.reserve(4096);
	}

	void Simulator::SimulatorImpl::AddLoad(ServiceLoad const& load)
	{
		int32_t slotStart = step_;
		int32_t slotEnd = slotStart + load.DurationSteps;
		int reqAdded = load.RequestCount;
		if (slotEnd > totalRequests_.size())
		{
			totalRequests_.resize(slotEnd, 0);
		}
		for (; slotStart < slotEnd; ++slotStart)
		{
			totalRequests_[slotStart] += reqAdded;
		}
	}

	void Simulator::SimulatorImpl::AddRule(ScaleRule const& rule, int32_t interval)
	{
		rules_.push_back(ScaleRuleSlot(rule, interval));
	}

	void Simulator::SimulatorImpl::OneStep()
	{
		this->StepAt(step_);
		++step_;
	}

	ServerStatus Simulator::SimulatorImpl::GetServerStatus() const
	{
		ServerStatus status;
		if (step_>0){
			status.InstanceCount = instances_.back();
			status.TotalRequests = RequestAtStep(step_ - 1);
		}
		else
		{
			status.InstanceCount = minInstances_;
			status.TotalRequests = 0;
		}
		return status;
	}

	LoadStatus Simulator::SimulatorImpl::GetRecentLoadStatus() const
	{
		LoadStatus status;
		status.RequestComing = requestOfLastStep_;
		int32_t nth = RequestAtStep(step_ - 1);
		int32_t n_1th = RequestAtStep(step_ - 2);
		status.RequestComplete = n_1th - nth + status.RequestComing;
		return status;
	}

	void Simulator::SimulatorImpl::StepAt(int32_t step)
	{
		ServiceLoad load;
		if (generator_.NextLoad(step, load))
		{
			requestOfLastStep_ = load.RequestCount;
			this->AddLoad(load);
		}
		else
		{
			requestOfLastStep_ = 0;
		}
		int instance = step == 0 ? minInstances_ : instances_[step - 1];
		int32_t loadPercentage = RequestAtStep(step) * 100 / (instance * CAP_PER_INSTANCE);
		bool ruleApplied = false;
		for (auto it = rules_.begin(); it != rules_.end(); ++it)
		{
			it->ApplyRule(step, loadPercentage, instance, ruleApplied);
		}
		if (ruleApplied){
			instance = instance < maxInstances_ ? instance : maxInstances_;
			instance = instance > minInstances_ ? instance : minInstances_;
		}
		instances_.push_back(instance);
	}

	inline int32_t Simulator::SimulatorImpl::RequestAtStep(int32_t step) const
	{
		return totalRequests_.size() > step ? totalRequests_[step] : 0;
	}

	void Simulator::SimulatorImpl::SetLoadGenerator(LoadGenerator&& generator)
	{
		this->generator_ = std::move(generator);
	}

	bool Simulator::SimulatorImpl::Finished() const
	{
		return step_ >= generator_.GetEndStep() && RequestAtStep(step_) == 0;
	}
}